# -*- coding: utf-8 -*-
"""
可视化图表模块

提供多种数据可视化功能
"""

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
import base64
import io
from typing import Dict, List, Any, Optional, Tuple
from pathlib import Path
try:
    from ..core.config import Config
    from ..utils.logger import LoggerMixin
except ImportError:
    # 如果相对导入失败，尝试绝对导入
    import sys
    import os
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from core.config import Config
    from utils.logger import LoggerMixin

# 支持中文显示
import matplotlib
matplotlib.use('Agg')  # 使用非交互式后端
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS', 'Microsoft YaHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 清理字体缓存以确保字体设置生效
try:
    import matplotlib.font_manager as fm
    fm._rebuild()
except:
    pass


class ChartGenerator(LoggerMixin):
    """图表生成器"""
    
    def __init__(self, config: Optional[Config] = None):
        super().__init__()
        self.config = config or Config()
        self.output_dir = Path(self.config.get('output.base_directory', 'output'))
        self.dpi = self.config.get('visualization.dpi', 300)
        self.figsize = tuple(self.config.get('visualization.figsize', [12, 8]))
        self.style = self.config.get('visualization.style', 'whitegrid')
        
        # 强制设置中文字体
        plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS', 'Microsoft YaHei']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 设置样式
        sns.set_style(self.style)
        sns.set_palette("husl")
    
    def create_correlation_heatmap(self, data: pd.DataFrame, 
                                 features: Optional[List[str]] = None,
                                 save_path: Optional[str] = None) -> Dict[str, Any]:
        """创建相关性热力图
        
        Args:
            data: 输入数据
            features: 要分析的特征列表
            save_path: 保存路径
            
        Returns:
            图表信息和base64编码
        """
        try:
            # 选择数值特征
            if features is None:
                numeric_data = data.select_dtypes(include=[np.number])
            else:
                numeric_data = data[features].select_dtypes(include=[np.number])
            
            if numeric_data.empty:
                return {"error": "没有可用于相关性分析的数值特征"}
            
            # 智能过滤不适合热力图的特征
            filtered_data = self._filter_heatmap_features(numeric_data)
            
            # 检查是否有足够的数值列进行相关性分析
            if len(filtered_data.columns) < 2:
                excluded_features = set(numeric_data.columns) - set(filtered_data.columns)
                return {"error": f"过滤后只有{len(filtered_data.columns)}个有效数值列，需要至少2个。已排除: {list(excluded_features)}"}
            
            # 移除包含NaN值过多的列
            clean_data = filtered_data.dropna(axis=1, thresh=len(filtered_data) * 0.5)
            if len(clean_data.columns) < 2:
                return {"error": "数据中缺失值过多，无法生成有效的相关性热力图"}
            
            # 计算相关性矩阵
            correlation_matrix = clean_data.corr()
            
            # 检查相关性矩阵是否有效
            if correlation_matrix.isnull().all().all():
                return {"error": "无法计算有效的相关性矩阵，请检查数据质量"}
            
            # 设置固定的图表尺寸（缩小六分之一）
            fig_width = 10 * 5/6  # 约8.33
            fig_height = 7.5 * 5/6  # 约6.25
            
            # 创建图表
            fig, ax = plt.subplots(figsize=(fig_width, fig_height), dpi=self.dpi)
            
            # 设置背景色
            fig.patch.set_facecolor('white')
            ax.set_facecolor('white')
            
            # 生成热力图 - 使用更美观的配置
            mask = np.triu(np.ones_like(correlation_matrix, dtype=bool))
            
            # 创建自定义颜色映射
            from matplotlib.colors import LinearSegmentedColormap
            colors = ['#2E86AB', '#A23B72', '#F18F01', '#C73E1D']
            n_bins = 256
            custom_cmap = LinearSegmentedColormap.from_list('custom', colors, N=n_bins)
            
            # 绘制热力图
            heatmap = sns.heatmap(
                correlation_matrix,
                mask=mask,
                annot=True,
                cmap=custom_cmap,
                center=0,
                square=True,
                fmt='.3f',
                annot_kws={'size': 4, 'weight': 'bold', 'color': 'white'},  # 字体缩小一半
                cbar_kws={
                    "shrink": 0.7,
                    "aspect": 25,
                    "pad": 0.01,
                    "label": "相关系数"
                },
                linewidths=0.5,
                linecolor='white',
                ax=ax
            )
            
            # 移除标题和轴标签
            # ax.set_title('📊 特征相关性热力图', 
            #             fontsize=8,  # 字体缩小一半
            #             fontweight='bold', 
            #             pad=15,
            #             color='#2C3E50')
            
            # # 美化轴标签
            # ax.set_xlabel('特征', fontsize=7, fontweight='bold', color='#34495E')  # 字体缩小一半
            # ax.set_ylabel('特征', fontsize=7, fontweight='bold', color='#34495E')  # 字体缩小一半
            
            # 旋转标签以避免重叠
            plt.setp(ax.get_xticklabels(), rotation=45, ha='right', fontsize=5)  # 字体缩小一半
            plt.setp(ax.get_yticklabels(), rotation=0, fontsize=5)  # 字体缩小一半
            
            # 添加网格线
            ax.grid(False)
            
            # 美化颜色条（移除标签）
            cbar = heatmap.collections[0].colorbar
            cbar.ax.tick_params(labelsize=5)  # 字体缩小一半
            # cbar.set_label('相关系数', fontsize=6, fontweight='bold')  # 移除颜色条标签
            
            # 调整布局避免遮挡 - 增加更多边距
            plt.tight_layout(pad=3.0, rect=[0, 0, 1, 0.90])
            
            # 保存和编码
            result = self._save_and_encode_figure(fig, save_path, 'correlation_heatmap')
            plt.close(fig)
            
            result.update({
                "chart_type": "correlation_heatmap",
                "features_analyzed": list(clean_data.columns),
                "correlation_stats": {
                    "max_correlation": float(correlation_matrix.abs().max().max()),
                    "mean_correlation": float(correlation_matrix.abs().mean().mean()),
                    "strong_correlations": self._find_strong_correlations(correlation_matrix)
                }
            })
            
            return result
            
        except Exception as e:
            self.log_error(f"创建相关性热力图失败: {str(e)}")
            return {"error": f"创建相关性热力图失败: {str(e)}"}
    
    def _filter_heatmap_features(self, data: pd.DataFrame) -> pd.DataFrame:
        """过滤不适合热力图的特征
        
        Args:
            data: 输入的数值数据
            
        Returns:
            过滤后的数据
        """
        filtered_data = data.copy()
        columns_to_remove = []
        
        for column in filtered_data.columns:
            column_data = filtered_data[column]
            
            # 1. 过滤ID类列（通常是唯一值或接近唯一值）
            unique_ratio = column_data.nunique() / len(column_data)
            if unique_ratio > 0.95:  # 95%以上都是唯一值
                columns_to_remove.append(column)
                self.log_info(f"排除ID类特征: {column} (唯一值比例: {unique_ratio:.2%})")
                continue
            
            # 2. 过滤常量列（方差为0或接近0）
            if column_data.var() < 1e-10:  # 方差接近0
                columns_to_remove.append(column)
                self.log_info(f"排除常量特征: {column} (方差: {column_data.var():.2e})")
                continue
            
            # 3. 过滤只有少数几个值的离散数值列（可能是编码后的分类变量）
            if column_data.nunique() <= 3 and column_data.dtype in ['int64', 'int32']:
                # 检查是否是0/1编码或1/2/3这样的简单编码
                unique_values = sorted(column_data.dropna().unique())
                if len(unique_values) <= 3 and all(isinstance(x, (int, np.integer)) for x in unique_values):
                    if set(unique_values).issubset({0, 1}) or set(unique_values).issubset({1, 2, 3}):
                        columns_to_remove.append(column)
                        self.log_info(f"排除简单编码特征: {column} (值: {unique_values})")
                        continue
            
            # 4. 过滤包含特定关键词的列名（通常是标识符）
            column_lower = column.lower()
            id_keywords = ['id', 'index', '编号', '序号', 'number', 'no', 'num']
            if any(keyword in column_lower for keyword in id_keywords):
                # 进一步检查是否真的是ID列
                if unique_ratio > 0.8:  # 80%以上唯一值才认为是ID
                    columns_to_remove.append(column)
                    self.log_info(f"排除标识符特征: {column} (包含ID关键词且唯一值比例: {unique_ratio:.2%})")
                    continue
            
            # 5. 过滤数据范围异常的列（可能是时间戳等）
            if column_data.max() > 1e10:  # 数值过大，可能是时间戳
                columns_to_remove.append(column)
                self.log_info(f"排除异常范围特征: {column} (最大值: {column_data.max():.2e})")
                continue
        
        # 移除标记的列
        filtered_data = filtered_data.drop(columns=columns_to_remove)
        
        if len(columns_to_remove) > 0:
            self.log_info(f"热力图特征过滤完成，排除了 {len(columns_to_remove)} 个不适合的特征")
        
        return filtered_data
    
    def create_cluster_scatter(self, data: pd.DataFrame, 
                             cluster_labels: List[int],
                             x_feature: str, y_feature: str,
                             save_path: Optional[str] = None) -> Dict[str, Any]:
        """创建聚类散点图
        
        Args:
            data: 输入数据
            cluster_labels: 聚类标签
            x_feature: X轴特征
            y_feature: Y轴特征
            save_path: 保存路径
            
        Returns:
            图表信息和base64编码
        """
        try:
            if x_feature not in data.columns or y_feature not in data.columns:
                return {"error": f"特征 {x_feature} 或 {y_feature} 不存在"}
            
            # 创建图表
            fig, ax = plt.subplots(figsize=self.figsize, dpi=self.dpi)
            
            # 创建散点图
            unique_labels = np.unique(cluster_labels)
            colors = plt.cm.Set3(np.linspace(0, 1, len(unique_labels)))
            
            for i, label in enumerate(unique_labels):
                mask = np.array(cluster_labels) == label
                if label == -1:  # 噪声点
                    ax.scatter(
                        data.loc[mask, x_feature],
                        data.loc[mask, y_feature],
                        c='black',
                        marker='x',
                        s=50,
                        alpha=0.6,
                        label='噪声点'
                    )
                else:
                    ax.scatter(
                        data.loc[mask, x_feature],
                        data.loc[mask, y_feature],
                        c=[colors[i]],
                        s=60,
                        alpha=0.7,
                        label=f'聚类 {label}'
                    )
            
            ax.set_xlabel(x_feature, fontsize=12)
            ax.set_ylabel(y_feature, fontsize=12)
            ax.set_title(f'聚类散点图: {x_feature} vs {y_feature}', fontsize=16, fontweight='bold')
            ax.legend()
            ax.grid(True, alpha=0.3)
            
            plt.tight_layout()
            
            # 保存和编码
            result = self._save_and_encode_figure(fig, save_path, 'cluster_scatter')
            plt.close(fig)
            
            result.update({
                "chart_type": "cluster_scatter",
                "x_feature": x_feature,
                "y_feature": y_feature,
                "n_clusters": len([l for l in unique_labels if l != -1]),
                "n_noise_points": int(np.sum(np.array(cluster_labels) == -1))
            })
            
            return result
            
        except Exception as e:
            self.log_error(f"创建聚类散点图失败: {str(e)}")
            return {"error": f"创建聚类散点图失败: {str(e)}"}
    
    def create_distribution_plots(self, data: pd.DataFrame, 
                                features: Optional[List[str]] = None,
                                save_path: Optional[str] = None) -> Dict[str, Any]:
        """创建分布图
        
        Args:
            data: 输入数据
            features: 要分析的特征列表
            save_path: 保存路径
            
        Returns:
            图表信息和base64编码
        """
        try:
            # 选择特征进行分布分析
            if features is None:
                # 如果没有指定特征，选择所有数值特征
                numeric_data = data.select_dtypes(include=[np.number])
                if numeric_data.empty:
                    return {"error": "没有可用于分布分析的数值特征"}
            else:
                # 如果指定了特征，检查这些特征是否存在
                missing_features = [f for f in features if f not in data.columns]
                if missing_features:
                    return {"error": f"指定的特征不存在: {missing_features}"}
                
                # 对于指定的特征，只选择数值类型的
                selected_data = data[features]
                numeric_data = selected_data.select_dtypes(include=[np.number])
                
                # 如果用户选择的特征中没有数值特征，返回错误
                if numeric_data.empty:
                    categorical_features = [f for f in features if f in data.columns and data[f].dtype == 'object']
                    if categorical_features:
                        return {"error": f"选择的特征 {features} 不是数值类型，无法生成直方图。请选择数值类型的特征如：{data.select_dtypes(include=[np.number]).columns.tolist()[:5]}"}
                    else:
                        return {"error": "选择的特征不是数值类型，无法生成直方图"}
            
            n_features = len(numeric_data.columns)
            if n_features > 12:  # 限制特征数量
                numeric_data = numeric_data.iloc[:, :12]
                self.log_warning(f"特征数量过多，只显示前12个特征")
            
            # 如果只有一个特征，创建单个大图
            if n_features == 1:
                fig, ax = plt.subplots(1, 1, figsize=(10, 6), dpi=self.dpi)
                column = numeric_data.columns[0]
                sns.histplot(data=numeric_data, x=column, kde=True, ax=ax)
                ax.set_title(f'{column} 分布', fontsize=14, fontweight='bold')
                ax.grid(True, alpha=0.3)
            else:
                # 计算子图布局
                n_cols = min(3, n_features)
                n_rows = (n_features + n_cols - 1) // n_cols
                
                fig, axes = plt.subplots(n_rows, n_cols, figsize=(n_cols * 5, n_rows * 4), dpi=self.dpi)
                
                # 统一处理axes为二维数组
                if n_rows == 1:
                    axes = axes.reshape(1, -1)
                elif n_cols == 1:
                    axes = axes.reshape(-1, 1)
                
                # 创建分布图
                for i, column in enumerate(numeric_data.columns):
                    row = i // n_cols
                    col = i % n_cols
                    ax = axes[row, col]
                    
                    # 直方图和密度曲线
                    sns.histplot(data=numeric_data, x=column, kde=True, ax=ax)
                    ax.set_title(f'{column} 分布', fontweight='bold')
                    ax.grid(True, alpha=0.3)
                
                # 隐藏多余的子图
                for i in range(n_features, n_rows * n_cols):
                    row = i // n_cols
                    col = i % n_cols
                    axes[row, col].set_visible(False)
            
            if n_features == 1:
                plt.suptitle(f'{numeric_data.columns[0]} 直方图分析', fontsize=16, fontweight='bold')
            else:
                plt.suptitle('特征分布图', fontsize=16, fontweight='bold')
            plt.tight_layout()
            
            # 保存和编码
            result = self._save_and_encode_figure(fig, save_path, 'distribution_plots')
            plt.close(fig)
            
            # 计算分布统计
            distribution_stats = {}
            for column in numeric_data.columns:
                stats = {
                    "mean": float(numeric_data[column].mean()),
                    "std": float(numeric_data[column].std()),
                    "skewness": float(numeric_data[column].skew()),
                    "kurtosis": float(numeric_data[column].kurtosis())
                }
                distribution_stats[column] = stats
            
            result.update({
                "chart_type": "distribution_plots",
                "features_analyzed": list(numeric_data.columns),
                "distribution_stats": distribution_stats
            })
            
            return result
            
        except Exception as e:
            self.log_error(f"创建分布图失败: {str(e)}")
            return {"error": f"创建分布图失败: {str(e)}"}
    

    
    def create_categorical_plots(self, data: pd.DataFrame, 
                               features: Optional[List[str]] = None,
                               save_path: Optional[str] = None) -> Dict[str, Any]:
        """创建分类特征图表
        
        Args:
            data: 输入数据
            features: 要分析的特征列表
            save_path: 保存路径
            
        Returns:
            图表信息和base64编码
        """
        try:
            # 选择分类特征
            if features is None:
                categorical_data = data.select_dtypes(include=['object', 'category'])
            else:
                categorical_data = data[features].select_dtypes(include=['object', 'category'])
            
            if categorical_data.empty:
                return {"error": "没有可用于分析的分类特征"}
            
            n_features = len(categorical_data.columns)
            if n_features > 6:  # 限制特征数量
                categorical_data = categorical_data.iloc[:, :6]
                self.log_warning(f"分类特征数量过多，只显示前6个特征")
            
            # 计算子图布局
            n_cols = min(2, n_features)
            n_rows = (n_features + n_cols - 1) // n_cols
            
            fig, axes = plt.subplots(n_rows, n_cols, figsize=(n_cols * 6, n_rows * 4), dpi=self.dpi)
            
            # 统一处理axes为二维数组
            if n_features == 1:
                axes = np.array([[axes]])
            elif n_rows == 1:
                axes = axes.reshape(1, -1)
            elif n_cols == 1:
                axes = axes.reshape(-1, 1)
            
            # 创建分类图表
            for i, column in enumerate(categorical_data.columns):
                row = i // n_cols
                col = i % n_cols
                ax = axes[row, col]
                
                # 计算值计数
                value_counts = categorical_data[column].value_counts()
                
                # 如果类别太多，只显示前10个
                if len(value_counts) > 10:
                    value_counts = value_counts.head(10)
                    ax.text(0.02, 0.98, '(仅显示前10个类别)', transform=ax.transAxes, 
                           verticalalignment='top', fontsize=8, style='italic')
                
                # 创建条形图
                bars = ax.bar(range(len(value_counts)), value_counts.values)
                ax.set_xticks(range(len(value_counts)))
                ax.set_xticklabels(value_counts.index, rotation=45, ha='right')
                ax.set_ylabel('频次')
                ax.set_title(f'{column} 分布', fontweight='bold')
                ax.grid(True, alpha=0.3, axis='y')
                
                # 添加数值标签
                for bar, value in zip(bars, value_counts.values):
                    height = bar.get_height()
                    ax.text(bar.get_x() + bar.get_width()/2., height + height*0.01,
                           f'{value}', ha='center', va='bottom', fontsize=8)
            
            # 隐藏多余的子图
            for i in range(n_features, n_rows * n_cols):
                row = i // n_cols
                col = i % n_cols
                axes[row, col].set_visible(False)
            
            plt.suptitle('分类特征分布图', fontsize=16, fontweight='bold')
            plt.tight_layout()
            
            # 保存和编码
            result = self._save_and_encode_figure(fig, save_path, 'categorical_plots')
            plt.close(fig)
            
            # 计算分类统计
            categorical_stats = {}
            for column in categorical_data.columns:
                stats = {
                    "unique_count": int(categorical_data[column].nunique()),
                    "most_frequent": str(categorical_data[column].mode().iloc[0]),
                    "most_frequent_count": int(categorical_data[column].value_counts().iloc[0]),
                    "distribution": categorical_data[column].value_counts().head(5).to_dict()
                }
                categorical_stats[column] = stats
            
            result.update({
                "chart_type": "categorical_plots",
                "features_analyzed": list(categorical_data.columns),
                "categorical_stats": categorical_stats
            })
            
            return result
            
        except Exception as e:
            self.log_error(f"创建分类特征图表失败: {str(e)}")
            return {"error": f"创建分类特征图表失败: {str(e)}"}
    
    def _save_and_encode_figure(self, fig, save_path: Optional[str], 
                              default_name: str) -> Dict[str, Any]:
        """保存图表并编码为base64"""
        try:
            # 确保字体设置生效
            plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS', 'Microsoft YaHei']
            plt.rcParams['axes.unicode_minus'] = False
            
            # 保存到文件
            if save_path:
                file_path = Path(save_path)
            else:
                file_path = self.output_dir / 'visualizations' / f'{default_name}.png'
            
            file_path.parent.mkdir(parents=True, exist_ok=True)
            fig.savefig(file_path, dpi=self.dpi, bbox_inches='tight', 
                       facecolor='white', edgecolor='none')
            
            # 编码为base64
            buffer = io.BytesIO()
            fig.savefig(buffer, format='png', dpi=self.dpi, bbox_inches='tight',
                       facecolor='white', edgecolor='none')
            buffer.seek(0)
            image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
            buffer.close()
            
            return {
                "success": True,
                "file_path": str(file_path),
                "image_base64": image_base64
            }
            
        except Exception as e:
            self.log_error(f"保存图表失败: {str(e)}")
            return {"error": f"保存图表失败: {str(e)}"}
    
    def _find_strong_correlations(self, correlation_matrix: pd.DataFrame, 
                                threshold: float = 0.7) -> List[Dict[str, Any]]:
        """找出强相关性"""
        strong_correlations = []
        
        for i in range(len(correlation_matrix.columns)):
            for j in range(i + 1, len(correlation_matrix.columns)):
                corr_value = correlation_matrix.iloc[i, j]
                if abs(corr_value) >= threshold:
                    strong_correlations.append({
                        "feature1": correlation_matrix.columns[i],
                        "feature2": correlation_matrix.columns[j],
                        "correlation": float(corr_value),
                        "strength": "强正相关" if corr_value > 0 else "强负相关"
                    })
        
        return strong_correlations
    
    def create_scatter_plot(self, data: pd.DataFrame, 
                          x_feature: str, y_feature: str,
                          color_feature: Optional[str] = None,
                          save_path: Optional[str] = None,
                          plot_type: str = 'scatter') -> Dict[str, Any]:
        """创建散点图
        
        Args:
            data: 输入数据
            x_feature: X轴特征
            y_feature: Y轴特征
            color_feature: 颜色分组特征（可选）
            save_path: 保存路径
            
        Returns:
            图表信息和base64编码
        """
        try:
            if x_feature not in data.columns or y_feature not in data.columns:
                return {"error": f"特征 {x_feature} 或 {y_feature} 不存在"}
            
            # 检查是否为数值特征
            if not pd.api.types.is_numeric_dtype(data[x_feature]) or not pd.api.types.is_numeric_dtype(data[y_feature]):
                return {"error": f"散点图需要数值类型的特征，{x_feature} 或 {y_feature} 不是数值类型"}
            
            # 创建图表
            fig, ax = plt.subplots(figsize=self.figsize, dpi=self.dpi)
            
            # 根据plot_type创建不同类型的图表
            if plot_type == 'line':
                # 创建折线图
                if color_feature and color_feature in data.columns:
                    # 按颜色分组的折线图
                    if not pd.api.types.is_numeric_dtype(data[color_feature]):
                        unique_values = data[color_feature].unique()
                        colors = plt.cm.Set3(np.linspace(0, 1, len(unique_values)))
                        for i, value in enumerate(unique_values):
                            mask = data[color_feature] == value
                            subset = data.loc[mask].sort_values(x_feature)
                            ax.plot(
                                subset[x_feature], subset[y_feature],
                                color=colors[i], label=str(value), 
                                alpha=0.8, linewidth=2, marker='o', markersize=4
                            )
                        ax.legend(title=color_feature, bbox_to_anchor=(1.05, 1), loc='upper left')
                    else:
                        # 数值颜色特征，简单折线图
                        sorted_data = data.sort_values(x_feature)
                        ax.plot(sorted_data[x_feature], sorted_data[y_feature], 
                               alpha=0.8, linewidth=2, marker='o', markersize=4)
                else:
                    # 简单折线图
                    sorted_data = data.sort_values(x_feature)
                    ax.plot(sorted_data[x_feature], sorted_data[y_feature], 
                           alpha=0.8, linewidth=2, marker='o', markersize=4)
            else:
                # 创建散点图
                if color_feature and color_feature in data.columns:
                    # 按颜色分组的散点图
                    if pd.api.types.is_numeric_dtype(data[color_feature]):
                        scatter = ax.scatter(
                            data[x_feature], data[y_feature], 
                            c=data[color_feature], cmap='viridis', 
                            alpha=0.7, s=60
                        )
                        plt.colorbar(scatter, ax=ax, label=color_feature)
                    else:
                        # 分类颜色特征
                        unique_values = data[color_feature].unique()
                        colors = plt.cm.Set3(np.linspace(0, 1, len(unique_values)))
                        for i, value in enumerate(unique_values):
                            mask = data[color_feature] == value
                            ax.scatter(
                                data.loc[mask, x_feature], 
                                data.loc[mask, y_feature],
                                c=[colors[i]], label=str(value), 
                                alpha=0.7, s=60
                            )
                        ax.legend(title=color_feature, bbox_to_anchor=(1.05, 1), loc='upper left')
                else:
                    # 简单散点图
                    ax.scatter(data[x_feature], data[y_feature], alpha=0.7, s=60)
            
            ax.set_xlabel(x_feature, fontsize=12)
            ax.set_ylabel(y_feature, fontsize=12)
            chart_title = f"{'折线图' if plot_type == 'line' else '散点图'}: {x_feature} vs {y_feature}"
            ax.set_title(chart_title, fontsize=16, fontweight='bold')
            ax.grid(True, alpha=0.3)
            
            plt.tight_layout()
            
            # 保存和编码
            result = self._save_and_encode_figure(fig, save_path, 'scatter_plot')
            plt.close(fig)
            
            # 计算相关性
            correlation = data[x_feature].corr(data[y_feature])
            
            result.update({
                "chart_type": f"{plot_type}_plot",
                "x_feature": x_feature,
                "y_feature": y_feature,
                "color_feature": color_feature,
                "correlation": float(correlation),
                "data_points": len(data)
            })
            
            return result
            
        except Exception as e:
            self.log_error(f"创建散点图失败: {str(e)}")
            return {"error": f"创建散点图失败: {str(e)}"}
    
    def create_pie_chart(self, data: pd.DataFrame, 
                        feature: str,
                        save_path: Optional[str] = None) -> Dict[str, Any]:
        """创建饼图
        
        Args:
            data: 输入数据
            feature: 要分析的特征
            save_path: 保存路径
            
        Returns:
            图表信息和base64编码
        """
        try:
            if feature not in data.columns:
                return {"error": f"特征 {feature} 不存在"}
            
            # 处理混合数据类型问题：将所有值转换为字符串
            # 这样可以避免在排序时出现字符串和数字比较的错误
            feature_data = data[feature].fillna('缺失值').astype(str)
            
            # 计算值计数
            value_counts = feature_data.value_counts()
            
            # 如果类别太多，只显示前8个，其余归为"其他"
            if len(value_counts) > 8:
                top_values = value_counts.head(7)
                others_count = value_counts.tail(len(value_counts) - 7).sum()
                value_counts = pd.concat([top_values, pd.Series([others_count], index=['其他'])])
            
            # 创建图表
            fig, ax = plt.subplots(figsize=self.figsize, dpi=self.dpi)
            
            # 创建饼图
            colors = plt.cm.Set3(np.linspace(0, 1, len(value_counts)))
            wedges, texts, autotexts = ax.pie(
                value_counts.values, 
                labels=value_counts.index,
                autopct='%1.1f%%',
                colors=colors,
                startangle=90
            )
            
            # 美化文本
            for autotext in autotexts:
                autotext.set_color('white')
                autotext.set_fontweight('bold')
            
            ax.set_title(f'{feature} 分布饼图', fontsize=16, fontweight='bold')
            
            plt.tight_layout()
            
            # 保存和编码
            result = self._save_and_encode_figure(fig, save_path, 'pie_chart')
            plt.close(fig)
            
            result.update({
                "chart_type": "pie_chart",
                "feature": feature,
                "categories": len(value_counts),
                "distribution": value_counts.to_dict()
            })
            
            return result
            
        except Exception as e:
            self.log_error(f"创建饼图失败: {str(e)}")
            return {"error": f"创建饼图失败: {str(e)}"}
    
    def create_comprehensive_dashboard(self, data: pd.DataFrame, 
                                     cluster_labels: Optional[List[int]] = None,
                                     save_path: Optional[str] = None) -> Dict[str, Any]:
        """创建综合仪表板
        
        Args:
            data: 输入数据
            cluster_labels: 聚类标签（可选）
            save_path: 保存路径
            
        Returns:
            仪表板信息
        """
        try:
            dashboard_results = {
                "success": True,
                "charts": {},
                "summary": {}
            }
            
            # 1. 相关性热力图
            heatmap_result = self.create_correlation_heatmap(data)
            if heatmap_result.get("success"):
                dashboard_results["charts"]["correlation_heatmap"] = heatmap_result
            
            # 2. 分布图
            dist_result = self.create_distribution_plots(data)
            if dist_result.get("success"):
                dashboard_results["charts"]["distribution_plots"] = dist_result
            

            
            # 4. 分类特征图
            cat_result = self.create_categorical_plots(data)
            if cat_result.get("success"):
                dashboard_results["charts"]["categorical_plots"] = cat_result
            
            # 5. 聚类散点图（如果有聚类标签）
            if cluster_labels is not None:
                numeric_cols = data.select_dtypes(include=[np.number]).columns
                if len(numeric_cols) >= 2:
                    scatter_result = self.create_cluster_scatter(
                        data, cluster_labels, numeric_cols[0], numeric_cols[1]
                    )
                    if scatter_result.get("success"):
                        dashboard_results["charts"]["cluster_scatter"] = scatter_result
            
            # 生成摘要
            dashboard_results["summary"] = {
                "total_charts": len([c for c in dashboard_results["charts"].values() if c.get("success")]),
                "data_shape": list(data.shape),
                "numeric_features": len(data.select_dtypes(include=[np.number]).columns),
                "categorical_features": len(data.select_dtypes(include=['object', 'category']).columns),
                "has_clusters": cluster_labels is not None
            }
            
            self.log_info(f"综合仪表板创建完成，包含 {dashboard_results['summary']['total_charts']} 个图表")
            
            return dashboard_results
            
        except Exception as e:
            self.log_error(f"创建综合仪表板失败: {str(e)}")
            return {"error": f"创建综合仪表板失败: {str(e)}"}